#include <math.h>
#include <intrin.h>

#include "synvoice.h"
#include "synchannel.h"
#include "synmixer.h"

extern Synth::Mixer mixer;
namespace Synth {

Voice::Voice() {
	playFlag = false;
	channel = 0;
	volAmp.params = (EnvParams*)&parameterTable[cp_Env1Attack];
	env2.params = (EnvParams*)&parameterTable[cp_Env2Attack];
	osc[0].paramOffset = cp_Osc1Waveform;
	osc[1].paramOffset = cp_Osc2Waveform;
	osc[2].paramOffset = cp_Osc3Waveform;
}

void Voice::ResetVoiceParameters() {
	for (int i = 0; i < cp_LastVoiceOne; i++) {
		parameterTable[i] = (float)channel->params[i] / 127.f;
		parameterTableInt[i] = channel->params[i];
	}
}

void Voice::Play(Channel *chan, int note, int vel, bool bend) {
	// Cannot use parameterTable in this function - it's not called in context
	if (bend) {
		if (volAmp.Gated()) {
			this->note = note;
			targetNote = (float)note;
			float porta_speed = channel->GetParam(cp_PortamentoSpeed);
			float f = porta_speed;
			noteDelta = (targetNote - curNote) / (f*f*f * BLOCKSIZE * 128);
			return;
		}
	}

	channel = chan;
	float porta_speed = channel->GetParam(cp_PortamentoSpeed);
	this->note = note;
	
	//curNote = (float)note;
	if (channel->lastNote)
		curNote = (float)channel->lastNote;
	else
		curNote = (float)note;

	targetNote = (float)note;
	float f = porta_speed;
	noteDelta = (targetNote - curNote) / (0.00001f + f*f*f * BLOCKSIZE * 128);
	this->vel = vel;
	playFlag = true;
}

void Voice::Stop() {
	//Move ADSRs into Release phase
	//When dead, do:
	//channel = 0;
	volAmp.KeyUp();
	env2.KeyUp();
}

void Voice::StopHard() {
	volAmp.phase = EP_DEAD;
	channel = 0;
}

void Voice::RenderAdd(Block lbuffer, Block rbuffer) {
	// Slide (legato, portamento, whatever)
	curNote += noteDelta;
	if ((noteDelta > 0.0f && curNote >= targetNote) ||
		(noteDelta < 0.0f && curNote <= targetNote)) {
		// Slide found it's target. Stop sliding.
		curNote = targetNote;
		noteDelta = 0.0f;
	}

	// Okay, first COMPUTE all the slow ones
	const int lfoSize = cp_LFO2Speed - cp_LFO1Speed;
	for (int i = 0; i < 2; i++) {
		parameterTable[ms_LFO1 + i] =
			lfo[i].GetValue(parameterTable[cp_LFO1Phase + lfoSize*i],
                            parameterTableInt[cp_LFO1Waveform + lfoSize*i],
							parameterTableInt[cp_LFO1Mode + lfoSize*i],
							parameterTable[cp_LFO1Speed + lfoSize*i]);
	}

	volAmp.SetScale(parameterTable[cp_Env1Amount]);
	env2.SetScale(parameterTable[cp_Env2Amount]);
	parameterTable[ms_Env1] = volAmp.GetValue();
	parameterTable[ms_Env2] = env2.GetValue();

	//Also, fill the givens
	parameterTable[ms_Note] = (float)(note - 63) / 32.0f;
	if (parameterTable[ms_Note] < -1)
		parameterTable[ms_Note] = -1;
	if (parameterTable[ms_Note] > 1)
		parameterTable[ms_Note] = 1;
	parameterTable[ms_Velocity] = (float)vel / 127.0f;

	//Then do all modulations
	for (int i = 0; i < NumModulations; i++) {
		int off = i * (cp_Mod2Source - cp_Mod1Source);
		ChannelParams destParam = (ChannelParams)channel->params[off + cp_Mod1Dest]; 
		ChannelParams srcParam = (ChannelParams)channel->params[off + cp_Mod1Source];
		float amount = channel->params[off + cp_Mod1Amount] / 127.f;
		float source_param_value = parameterTable[(ChannelParams)(unsigned char)(char)srcParam];
		parameterTable[(int)destParam] += source_param_value * amount;
	}

	Block vol_buf;
	Block env2_buf;

	volAmp.Render(vol_buf);
	env2.Render(env2_buf);
	//float amp = dB2lin(volAmp.GetValue()*50 - 50);
	if (volAmp.IsDead() && !playFlag) {
		channel = 0; //will trigger next check
	}
	if (!IsPlaying()) {
		playFlag = false;
		return;
	}

	//Okay, trigger note if necessary
	if (playFlag) {
		timeStamp = mixer.GetTimeStamp();
		volAmp.KeyDown();
		env2.KeyDown();
		if (parameterTableInt[cp_LFO1Sync])
			lfo[0].SetT(channel->GetParam(cp_LFO1Phase));
		if (parameterTableInt[cp_LFO2Sync])
			lfo[1].SetT(channel->GetParam(cp_LFO2Phase));
		group = 0;
		int tempgroup = 0;
		for (int i = 0; i < 3; i++) {
			int off = osc[i].paramOffset;
			int grStop1 = osc[i].Start(
				parameterTableInt[off + cp_Osc1Waveform], 
				parameterTableInt[off + cp_Osc1InstrHardSync], 
				(float)(note) + parameterTableInt[off + cp_Osc1Transpose] + parameterTable[off + cp_Osc1Detune]);
			if (grStop1) {
				channel->StopGroup(grStop1);
				tempgroup = grStop1;
			}
		}
		group = tempgroup;
		playFlag = false;
	}

	//Then Render!!!!
	Block temp[3], ltemp, rtemp;
	Clear(ltemp);
	Clear(rtemp);
	const int oscSize = cp_Osc2Volume - cp_Osc1Volume;

	for (int i = 0; i < 3; i++) {
		Clear(temp[i]);
		int off = i*oscSize;
		float detune = parameterTable[off + cp_Osc1Detune];
		float depitch = parameterTable[cp_VoicePitch] * 127.f;
		float xpose = parameterTable[off + cp_Osc1Transpose] * 127.f;

		osc[i].SetFreq(curNote + detune + depitch + xpose);
		if (parameterTable[off + cp_Osc1Volume] > 0.0f) {
			osc[i].Render(temp[i], i ? temp[i-1] : 0);
		}
	}

	for (int i = 0; i < 3; i++) {
		int off = i*oscSize;
		float vol = dB2lin(parameterTable[off + cp_Osc1Volume] * 50 - 42);
		PanVolume(ltemp, rtemp, temp[i], temp[i], vol, parameterTable[off + cp_Osc1Pan]);
	}

	lfilt.Render(ltemp, 
		parameterTable[cp_FilterCutoff],
		parameterTable[cp_FilterResonance],
		parameterTableInt[cp_FilterType]);

	rfilt.Render(rtemp, 
		parameterTable[cp_FilterCutoff],
		parameterTable[cp_FilterResonance],
		parameterTableInt[cp_FilterType]);

	FORBLOCK(i) {
		float amp_val = vol_buf[i];
		lbuffer[i] += ltemp[i] * amp_val;	
		rbuffer[i] += rtemp[i] * amp_val;	
		//amp += ampDelta/BLOCKSIZE;
	}
}

}